/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mapbox.ohos.gestures;

import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.HashMap;

import static com.mapbox.ohos.gestures.OhosGesturesManager.GESTURE_TYPE_MULTI_FINGER_TAP;

/**
 * Gesture detector handling multi tap gesture.
 */
public class MultiFingerTapGestureDetector extends MultiFingerGesture<MultiFingerTapGestureDetector.OnMultiFingerTapGestureListener> {


    /**
     * Maximum time in millis to lift the fingers to register a tap event.
     */
    private long multiFingerTapTimeThreshold;

    /**
     * Maximum movement in pixels allowed for any finger before rejecting this gesture.
     */
    private float multiFingerTapMovementThreshold;
    private boolean invalidMovement;
    private boolean pointerLifted;
    private int lastPointersDownCount;

    /**
     * MultiFingerTapGestureDetector
     *
     * @param context
     * @param gesturesManager
     */
    public MultiFingerTapGestureDetector(Context context, OhosGesturesManager gesturesManager) {
        super(context, gesturesManager);
    }

    /**
     * OnMultiFingerTapGestureListener
     */
    public interface OnMultiFingerTapGestureListener {
        boolean onMultiFingerTap(MultiFingerTapGestureDetector detector, int pointersCount);
    }

    /**
     * analyzeEvent
     *
     * @param motionEvent
     * @return boolean
     */
    @Override
    protected boolean analyzeEvent(TouchEvent motionEvent) {
        super.analyzeEvent(motionEvent);

        int action = motionEvent.getAction();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (pointerLifted) {
                    invalidMovement = true;
                }
                lastPointersDownCount = pointerIdList.size();
                break;

            case TouchEvent.OTHER_POINT_UP:
                pointerLifted = true;
                break;

            case TouchEvent.PRIMARY_POINT_UP:
                boolean canExecute = canExecute(GESTURE_TYPE_MULTI_FINGER_TAP);
                boolean handled = false;
                if (canExecute) {
                    handled = listener.onMultiFingerTap(this, lastPointersDownCount);
                }
                reset();
                return handled;

            case TouchEvent.POINT_MOVE:
                if (invalidMovement) {
                    break;
                }
                invalidMovement = exceededMovementThreshold(pointersDistanceMap);
                break;

            default:
                break;
        }

        return false;
    }

    boolean exceededMovementThreshold(HashMap<PointerDistancePair, MultiFingerDistancesObject> map) {
        for (MultiFingerDistancesObject distancesObject : map.values()) {
            float diffX = Math.abs(distancesObject.getCurrFingersDiffX() - distancesObject.getPrevFingersDiffX());
            float diffY = Math.abs(distancesObject.getCurrFingersDiffY() - distancesObject.getPrevFingersDiffY());

            invalidMovement = diffX > multiFingerTapMovementThreshold || diffY > multiFingerTapMovementThreshold;

            if (invalidMovement) {
                return true;
            }
        }

        return false;
    }

    @Override
    protected boolean canExecute(int invokedGestureType) {
        return lastPointersDownCount > 1 && !invalidMovement && getGestureDuration() < multiFingerTapTimeThreshold
            && super.canExecute(invokedGestureType);
    }

    @Override
    protected void reset() {
        super.reset();
        lastPointersDownCount = 0;
        invalidMovement = false;
        pointerLifted = false;
    }

    /**
     * Get maximum time in millis that fingers can have a contact with the screen before rejecting this gesture.
     *
     * @return maximum touch time for tap gesture.
     */
    public long getMultiFingerTapTimeThreshold() {
        return multiFingerTapTimeThreshold;
    }

    /**
     * Set maximum time in millis that fingers can have a contact with the screen before rejecting this gesture.
     *
     * @param multiFingerTapTimeThreshold maximum touch time for tap gesture.
     */
    public void setMultiFingerTapTimeThreshold(long multiFingerTapTimeThreshold) {
        this.multiFingerTapTimeThreshold = multiFingerTapTimeThreshold;
    }

    /**
     * Get maximum movement allowed for any finger before rejecting this gesture.
     *
     * @return movement threshold in pixels.
     */
    public float getMultiFingerTapMovementThreshold() {
        return multiFingerTapMovementThreshold;
    }

    /**
     * Set maximum movement allowed in pixels for any finger before rejecting this gesture.
     * <p>
     * We encourage to set those values from dimens to accommodate for various screen sizes.
     *
     * @param multiFingerTapMovementThreshold movement threshold.
     */
    public void setMultiFingerTapMovementThreshold(float multiFingerTapMovementThreshold) {
        this.multiFingerTapMovementThreshold = multiFingerTapMovementThreshold;
    }

    /**
     * Set maximum movement allowed in dp for any finger before rejecting this gesture.
     *
     * @param multiFingerTapMovementThresholdDimen movement threshold.
     */
    public void setMultiFingerTapMovementThresholdResource(int multiFingerTapMovementThresholdDimen) {
        try {
            setMultiFingerTapMovementThreshold(Utils.fpToPx(context.getResourceManager().getElement(multiFingerTapMovementThresholdDimen).getFloat(), context));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
